import * as THREE from 'three';
import { Material, Object3D } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import Stats from 'three/examples/jsm/libs/stats.module';
export interface TextTureInfo {
    ImageUrl: string;
    Texturename: string;
    Texture: any;
    Color: any;
    transparent: boolean;
    opacity: number;
}

export default class ThreeBase {
    public clock: any;
    public scene: any;
    public ground: any;
    public camera: any;
    public doRenderFunc: any;
    public mouse: any;
    public raycaster: any = new THREE.Raycaster();
    public INTERSECTED: any;
    public mixer: any;
    public modelchilren: any[] = new Array();
    protected near: number = 1;
    protected far: number = 2000;
    protected fov: number = 75;
    private renderer: any;
    private stats: any;
    private element: any;
    private hemiLight: any;


    constructor(element: any, iscontrol: boolean = true, fov: number = 75, far: number = 2000) {
        this.fov = fov;
        this.far = far;
        this.element = element;
        this.init();
        this.animate();
        if (iscontrol) {
            this.AddControl();
        }
    }

    public LoadObject(objecturl: string, mlurl: string, scale: number, x: number, y: number, z: number) {
        new MTLLoader()
            .load(mlurl, (materials) => {
                materials.preload();
                new OBJLoader()
                    .setMaterials(materials)
                    .load(objecturl, (object) => {
                        object.scale.set(scale, scale, scale);
                        object.position.x = x;
                        object.position.y = y;
                        object.position.z = z;
                        this.scene.add(object);
                    }, this.onProgress, this.onError);
            });
    }

    public async LoadObjectImage(
        objecturl: string,
        imageinfos: TextTureInfo[],
        scale: number, x: number, y: number, z: number, rotationY: number) {
        imageinfos.forEach((image) => {
            if (image.ImageUrl) {
                const texture = new THREE.TextureLoader().load(image.ImageUrl);
                image.Texture = texture;
            }
        });
        return new Promise((resolve, reject) => {
            new OBJLoader().load(objecturl, (object) => {
                // console.log(object);
                object.traverse((child: any) => {
                    if (child.isMesh) {
                        imageinfos.forEach((image) => {
                            image.Texturename.split(',').forEach((name) => {
                                if (child.name.indexOf(name) > -1) {
                                    if (image.ImageUrl) {
                                        child.material.map = image.Texture;
                                    } else if (image.Color) {
                                        child.material.color = image.Color;
                                    }
                                    if (image.transparent) {
                                        child.material.transparent = image.transparent;
                                    }
                                    if (image.opacity) {
                                        child.material.opacity = image.opacity;
                                    }
                                }
                            });

                        });
                    }
                    this.modelchilren.push(child);
                });
                object.scale.set(scale, scale, scale);
                object.position.x = x;
                object.position.y = y;
                object.position.z = z;
                object.rotation.y = rotationY;
                this.scene.add(object);
                resolve();
                // console.log(this.modelchilren);
            }, this.onProgress, (error) => {
                reject();
            });
        });
    }

    public onProgress = (process) => {
        console.log('loaded:' + (100 * 1.0 * process.loaded / process.total));
    }

    public onError = (error) => {
        console.error(error);
    }


    public async LoadModelGltf(
        url: string,
        scale: number,
        position: any,
        rotation: any,
        animationname?: string,
        meshName?: string,
        envMap?: any,
        isDRACOLoader?: true) {
        // model
        const loader = new GLTFLoader();
        if (isDRACOLoader) {
            const dracoloader = new DRACOLoader();
            dracoloader.setDecoderPath('./draco/gltf/');
            loader.setDRACOLoader(dracoloader);
        }
        return new Promise((resolve, reject) => {
            loader.load(url, (gltf) => {
                // const object = SkeletonUtils.clone(gltf.scene);

                const model = gltf.scene;
                model.scale.set(scale, scale, scale);
                model.position.set(position.x, position.y, position.z);
                model.rotation.set(rotation.x, rotation.y, rotation.z);
                if (envMap) {
                    model.traverse((child: any) => {
                        if (child.isMesh) {
                            child.material.envMap = envMap;
                            child.castShadow = true;
                            child.receiveShadow = true;
                        }
                        this.modelchilren.push(child);
                    });
                } else {
                    model.traverse((child: any) => {
                        this.modelchilren.push(child);
                    });
                }
                // model.children[0].children[0].children[0].
                // children[0].children[0].children[0].children.forEach((e) => {
                //     this.modelchilren.push(e);
                // });
                // this.scene.add(this.modelgroup);
                this.scene.add(model);


                //
                // this.modelchilren.forEach((o: any) => {

                //     if (o.name) {
                //         console.log(o.name);
                //         if (o.name === 'Head_1') {
                //             const geometry = new THREE.BoxBufferGeometry(0.01, 0.01, 0.01);
                //             const material = new THREE.MeshBasicMaterial({ color: 0xC71585 });
                //             const cube = new THREE.Mesh(geometry, material);
                //             cube.position.set(0, 0.01, 0);
                //             o.add(cube);
                //         }
                //     }
                // });

                if (animationname) {
                    const skinnedMesh = model.getObjectByName(meshName as string);
                    this.mixer = this.startAnimation(skinnedMesh, gltf.animations, animationname);
                    this.mixer.clipAction(gltf.animations[0]).play();
                    this.animate();
                }

                // console.log(this.modelchilren);
                resolve(model);

            }, (process) => {
                console.log('loaded:' + (100 * 1.0 * process.loaded / process.total));
            }, (error) => {
                console.error(error);
                reject();
            });
        });
    }

    public LoadModelFBX(
        url: string,
        scale: number,
        position: any,
        rotation: any,
        animationname?: string,
        meshName?: string,
        envMap?: any) {
        // model
        const loader = new FBXLoader();
        return new Promise((resolve, reject) => {
            loader.load(url, (model: any) => {
                // const object = SkeletonUtils.clone(gltf.scene);
                model.scale.set(scale, scale, scale);
                model.position.set(position.x, position.y, position.z);
                model.rotation.set(rotation.x, rotation.y, rotation.z);
                if (envMap) {
                    model.traverse((child: any) => {
                        if (child.isMesh) {
                            child.material.envMap = envMap;
                            child.castShadow = true;
                            child.receiveShadow = true;
                        }
                        this.modelchilren.push(child);
                    });
                } else {
                    model.traverse((child: any) => {
                        this.modelchilren.push(child);
                    });
                }

                this.scene.add(model);


                if (animationname) {
                    const skinnedMesh = model.getObjectByName(meshName as string);
                    this.mixer = this.startAnimation(skinnedMesh, model.animations, animationname);
                    this.mixer.clipAction(model.animations[0]).play();
                    this.animate();
                }

                // console.log(this.modelchilren);
                resolve(model);

            }, (process) => {
                console.log('loaded:' + (100 * 1.0 * process.loaded / process.total));
            }, (error) => {
                console.error(error);
                reject();
            });
        });
    }

    public LoadModelFBXModel(url: string): Promise<any> {
        // model
        const loader = new FBXLoader();
        return new Promise((resolve, reject) => {
            loader.load(url, (model: any) => {
                console.log(model);
                resolve(model);
            }, (process) => {
                console.log('loaded:' + (100 * 1.0 * process.loaded / process.total));
            }, (error) => {
                console.error(error);
                reject();
            });
        });
    }

    public RemoveGround() {
        this.scene.remove(this.ground);
    }

    public SetSize(object: any, scale: number): void {
        object.scale.set(scale, scale, scale);
    }

    /**
     * Start animation for a specific mesh object. Find the animation by name in the 3D model's animation array
     * @param skinnedMesh {THREE.SkinnedMesh} The mesh to animate
     * @param animations {Array} Array containing all the animations for this model
     * @param animationName {string} Name of the animation to launch
     * @return {THREE.AnimationMixer} Mixer to be used in the render loop
     */
    public startAnimation(skinnedMesh, animations, animationName) {
        const mixer = new THREE.AnimationMixer(skinnedMesh);
        const clip = THREE.AnimationClip.findByName(animations, animationName);
        if (clip) {
            const action = mixer.clipAction(clip);
            action.play();
        }
        return mixer;
    }

    public AddbackgroundWithResource(backcolor: any) {
        this.scene.background = new THREE.Color(backcolor);
    }

    public AddLightWithPosition(position: any) {
        const dirLight = new THREE.DirectionalLight(0xffffff, 1);
        dirLight.position.set(position.x, position.y, position.z);
        dirLight.position.multiplyScalar(100);
        this.scene.add(dirLight);
    }

    private init() {
        this.clock = new THREE.Clock();
        this.camera = new THREE.PerspectiveCamera(this.fov,
            window.innerWidth / window.innerHeight, this.near, this.far);
        this.camera.position.z = -100;
        this.scene = new THREE.Scene();
        this.mouse = new THREE.Vector2();
        //
        this.renderer = new THREE.WebGLRenderer(
            {
                antialias: true, // true/false表示是否开启反锯齿
                alpha: true, // true/false 表示是否可以设置背景色透明
                precision: 'mediump', // highp/mediump/lowp 表示着色精度选择
                premultipliedAlpha: false, // true/false 表示是否可以设置像素深度（用来度量图像的分辨率）
                preserveDrawingBuffer: true, // true/false 表示是否保存绘图缓冲
                stencil: false, // false/true 表示是否使用模板字体或图案
                powerPreference: 'high-performance',
            },
        );
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(window.innerWidth, window.innerHeight);

        this.renderer.gammaInput = true;
        this.renderer.gammaOutput = true;
        this.renderer.gammaFactor = 2.2;

        this.Addbackground();
        this.AddLight();
        // this.addGround();


        this.element.appendChild(this.renderer.domElement);
        //
        this.stats = new Stats();

        this.element.appendChild(this.stats.dom);
        //
        window.addEventListener('resize', this.onWindowResize, false);

        this.renderer.render(this.scene, this.camera);

        document.addEventListener('mousemove', this.onDocumentMouseMove, false);

    }


    private onDocumentMouseMove = (event) => {
        event.preventDefault();
        this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        this.mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
    }

    /**
     * 添加灯光
     */
    private AddLight() {
        // LIGHTS
        this.hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 1);
        // this.hemiLight.color.setHSL(0.6, 1, 0.6);
        // this.hemiLight.groundColor.setHSL(0.095, 1, 0.75);
        this.hemiLight.position.set(0, 0, 0);
        // this.scene.add(this.hemiLight);

        const dirLight = new THREE.DirectionalLight(0xffffff, 1);
        dirLight.position.set(0, 100, 0);
        dirLight.position.multiplyScalar(100);
        this.scene.add(dirLight);
    }



    /**
     * 添加天空盒子
     */
    private Addbackground() {
        const urls = ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'];
        this.scene.background = new THREE.CubeTextureLoader()
            .setPath('./sky/carsky/').load(
                urls,
            );
    }

    /**
     * 添加地面
     */
    private addGround() {
        // const ground = new THREE.Mesh(
        //     new THREE.PlaneBufferGeometry( 2400, 2400 ),
        //     new THREE.ShadowMaterial( { colorWrite: true, opacity: 0.15, depthWrite: false },
        //     ) );
        // ground.rotation.x = - Math.PI / 2;
        // ground.receiveShadow = true;
        // ground.renderOrder = 1;
        // this.scene.add( ground );

        // // GROUND
        // const groundGeo = new THREE.PlaneBufferGeometry(2400, 2400);
        // const groundMat = new THREE.MeshLambertMaterial({color: 0xffffff,opacity: 0.15});
        // groundMat.color.setHSL(0.095, 1, 0.75);
        // const ground = new THREE.Mesh(groundGeo, groundMat);
        // ground.rotation.x = -Math.PI / 2;
        // ground.receiveShadow = true;
        // this.scene.add(ground);

        // const grid = new THREE.GridHelper(10000, 1000, 0x000000, 0x000000);
        // if (grid.material instanceof Material) {
        //     grid.material.opacity = 0.2;
        //     grid.material.depthWrite = false;
        //     grid.material.transparent = true;
        // }
        // this.scene.add(grid);

        // GROUND
        const groundGeo = new THREE.PlaneBufferGeometry(300, 200);
        const groundMat = new THREE.MeshLambertMaterial({ color: 0x374049 });
        // groundMat.color.setHSL(0.095, 1, 0.75);
        this.ground = new THREE.Mesh(groundGeo, groundMat);
        this.ground.position.y = 0;
        this.ground.position.z -= 0;
        this.ground.rotation.x = -Math.PI / 2;
        this.ground.receiveShadow = true;
        this.ground.name = 'ground';
        this.scene.add(this.ground);
    }

    /**
     * 自适应
     */
    private onWindowResize = () => {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }

    private animate = () => {
        requestAnimationFrame(this.animate);
        this.render();
        this.stats.update();
        if (this.mixer) {
            const delta = this.clock.getDelta();
            this.mixer.update(delta);
        }
    }

    private render = () => {
        if (this.doRenderFunc) {
            this.doRenderFunc();
        }
        this.renderer.render(this.scene, this.camera);
    }

    private AddControl() {
        const controls = new OrbitControls(this.camera);
        controls.minPolarAngle = 1;
        controls.minDistance = this.near;
        controls.maxDistance = this.far;

        // controls.minAzimuthAngle = 80 * Math.PI / 180; // radians
        // controls.maxAzimuthAngle =  -230 * Math.PI / 180; // radians
        controls.minPolarAngle = 0; // radians
        controls.maxPolarAngle = Math.PI; // radians
        controls.target = this.camera.position;
        controls.enablePan = true;
    }
}
